#include "EasyXMen_sm3.h"
#include "EasyXMen_sm2_Cfg.h"
#include "istd_lib.h"

#if (CPU_ENDIAN_TYPE == CPU_LITTLE_ENDIAN)
#define cpu_to_be32(v) (((v) >> 24) | (((v) >> 8) & 0xff00) | (((v) << 8) & 0xff0000) | ((v) << 24))
#else /*BIGENDIAN*/
#define cpu_to_be32(v) (v)
#endif /*(CPU_ENDIAN_TYPE == CPU_LITTLE_ENDIAN)*/

/* Rotate data to the left */
#define XMEN_ROT_LEFT(X, n) (((X) << (n)) | ((X) >> (32 - (n))))

#define XMEN_P0(x) ((x) ^ XMEN_ROT_LEFT((x), 9) ^ XMEN_ROT_LEFT((x), 17))
#define XMEN_P1(x) ((x) ^ XMEN_ROT_LEFT((x), 15) ^ XMEN_ROT_LEFT((x), 23))

#define XMEN_FF0(x, y, z) ((x) ^ (y) ^ (z))
#define XMEN_FF1(x, y, z) (((x) & (y)) | ((x) & (z)) | ((y) & (z)))

#define XMEN_GG0(x, y, z) ((x) ^ (y) ^ (z))
#define XMEN_GG1(x, y, z) (((x) & (y)) | ((~(x)) & (z)))

/**
 * @description Performs the SM3 hash compression function on a single 512-bit block.
 *        This function updates the given digest with the result of processing the input block.
 *        It applies the SM3 algorithm's compression function, which includes message expansion,
 *        and iterative transformation of the working variables to produce the final hash value.
 * @param[in,out] digest : Pointer to an array of 8 unsigned integers representing the current state of the hash.
 *                         The function modifies this array to reflect the updated hash state after processing the block.
 * @param[in]     block  : Pointer to a 64-byte (512-bit) block of data to be processed.
 * @return void
 */
static void EasyXMen_sm3_compress(unsigned int xmen_digest[8], const unsigned char block[64])
{
	int j;
	unsigned int W[68], W1[64];
	const unsigned int *pblock = (const unsigned int *)block;

	unsigned int A = xmen_digest[0];
	unsigned int B = xmen_digest[1];
	unsigned int C = xmen_digest[2];
	unsigned int D = xmen_digest[3];
	unsigned int E = xmen_digest[4];
	unsigned int F = xmen_digest[5];
	unsigned int G = xmen_digest[6];
	unsigned int H = xmen_digest[7];
	unsigned int SS1, SS2, TT1, TT2, T[64];

	for (j = 0; j < 16; j++)
	{
		W[j] = cpu_to_be32(pblock[j]);
	}
	for (j = 16; j < 68; j++)
	{
		W[j] = XMEN_P1(W[j - 16] ^ W[j - 9] ^ XMEN_ROT_LEFT(W[j - 3], 15)) ^ XMEN_ROT_LEFT(W[j - 13], 7) ^ W[j - 6];
		;
	}
	for (j = 0; j < 64; j++)
	{
		W1[j] = W[j] ^ W[j + 4];
	}

	for (j = 0; j < 16; j++)
	{

		T[j] = 0x79CC4519;
		SS1 = XMEN_ROT_LEFT((XMEN_ROT_LEFT(A, 12) + E + XMEN_ROT_LEFT(T[j], j)), 7);
		SS2 = SS1 ^ XMEN_ROT_LEFT(A, 12);
		TT1 = XMEN_FF0(A, B, C) + D + SS2 + W1[j];
		TT2 = XMEN_GG0(E, F, G) + H + SS1 + W[j];
		D = C;
		C = XMEN_ROT_LEFT(B, 9);
		B = A;
		A = TT1;
		H = G;
		G = XMEN_ROT_LEFT(F, 19);
		F = E;
		E = XMEN_P0(TT2);
	}

	for (j = 16; j < 64; j++)
	{

		T[j] = 0x7A879D8A;
		SS1 = XMEN_ROT_LEFT((XMEN_ROT_LEFT(A, 12) + E + XMEN_ROT_LEFT(T[j], j % 32)), 7);
		SS2 = SS1 ^ XMEN_ROT_LEFT(A, 12);
		TT1 = XMEN_FF1(A, B, C) + D + SS2 + W1[j];
		TT2 = XMEN_GG1(E, F, G) + H + SS1 + W[j];
		D = C;
		C = XMEN_ROT_LEFT(B, 9);
		B = A;
		A = TT1;
		H = G;
		G = XMEN_ROT_LEFT(F, 19);
		F = E;
		E = XMEN_P0(TT2);
	}

	xmen_digest[0] ^= A;
	xmen_digest[1] ^= B;
	xmen_digest[2] ^= C;
	xmen_digest[3] ^= D;
	xmen_digest[4] ^= E;
	xmen_digest[5] ^= F;
	xmen_digest[6] ^= G;
	xmen_digest[7] ^= H;
}
/**
 * @description Initializes the SM3 hash context with predefined initial values.
 *        This function sets up the `sm3_ctx_t` structure for subsequent hashing operations,
 *        including setting the initial hash values and resetting counters.
 * @param[in,out] ctx : Pointer to the SM3 context structure to initialize.
 *                      The function modifies this structure to set up the initial state required for hashing.
 * @return void
 */
void EasyXMen_sm3_init(sm3_ctx_t *ctx)
{
	ctx->digest[0] = 0x7380166F;
	ctx->digest[1] = 0x4914B2B9;
	ctx->digest[2] = 0x172442D7;
	ctx->digest[3] = 0xDA8A0600;
	ctx->digest[4] = 0xA96F30BC;
	ctx->digest[5] = 0x163138AA;
	ctx->digest[6] = 0xE38DEE4D;
	ctx->digest[7] = 0xB0FB0E4E;

	ctx->nblocks = 0;
	ctx->num = 0;
}
/**
 * @description Updates the SM3 hash context with new data.
 *        This function processes additional input data to update the ongoing hash computation.
 *        It handles partial blocks and full blocks of data, ensuring that all input is correctly processed.
 * @param[in,out] ctx     : Pointer to the SM3 context structure to update.
 *                          The function modifies this structure to include the new data in the hash computation.
 * @param[in]     data    : Pointer to the byte array containing the new data to hash.
 * @param[in]     data_len: Length of the new data byte array in bytes.
 * @return void
 */

void EasyXMen_sm3_update(sm3_ctx_t *ctx, const unsigned char *data, unsigned int data_len)
{
	/*If there is remaining data in the context block from a previous update call*/
	if (ctx->num)
	{
		/* Calculate how much space is left in the current block */
		unsigned int left = SM3_BLOCK_SIZE - ctx->num;
		if (data_len < left)
		{
			/* Copy all the new data into the remaining space of the current block */
			memcpy(ctx->block + ctx->num, data, data_len);
			/* Update the number of bytes stored in the current block */
			ctx->num += data_len;
			return;
		}
		else
		{
			/* Fill up the current block with as much new data as it can hold */
			memcpy(ctx->block + ctx->num, data, left);
			/* Compress the now full block */
			EasyXMen_sm3_compress(ctx->digest, ctx->block);
			/* Increment the block count */
			ctx->nblocks++;
			/* Move the data pointer forward past the used data */
			data += left;
			/* Decrease the remaining data length by the amount used */
			data_len -= left;
		}
	}
	/*Process all complete blocks in the new data*/
	while (data_len >= SM3_BLOCK_SIZE)
	{
		/* Compress each complete block */
		EasyXMen_sm3_compress(ctx->digest, data);
		/* Increment the block count for each processed block */
		ctx->nblocks++;
		/* Move the data pointer forward by one block size */
		data += SM3_BLOCK_SIZE;
		/* Decrease the remaining data length by one block size */
		data_len -= SM3_BLOCK_SIZE;
	}
	/*Store any remaining data that doesn't fill a complete block*/
	ctx->num = data_len;
	if (data_len)
	{
		/* Copy any remaining data into the beginning of the block buffer */
		memcpy(ctx->block, data, data_len);
	}
}
/**
 * @description Finalizes the SM3 hash computation and produces the final hash digest.
 *        This function processes any remaining data in the context, pads the message,
 *        appends the message length, and performs the final compression to produce the hash output.
 * @param[in,out] ctx    : Pointer to the SM3 context structure containing the current state of the hash computation.
 *                         The function modifies this structure as part of finalizing the hash.
 * @param[out]    digest : Pointer to an output buffer where the resulting 32-byte (256-bit) hash will be stored.
 * @return void
 */
void EasyXMen_sm3_final(sm3_ctx_t *ctx, unsigned char *digest)
{
	int i;
	unsigned int *pdigest = (unsigned int *)digest;
	unsigned int *count = (unsigned int *)(ctx->block + SM3_BLOCK_SIZE - 8);

	ctx->block[ctx->num] = 0x80;

	if (ctx->num + 9 <= SM3_BLOCK_SIZE)
	{
		ILib_memset(ctx->block + ctx->num + 1, 0, SM3_BLOCK_SIZE - ctx->num - 9);
	}
	else
	{
		ILib_memset(ctx->block + ctx->num + 1, 0, SM3_BLOCK_SIZE - ctx->num - 1);
		EasyXMen_sm3_compress(ctx->digest, ctx->block);
		ILib_memset(ctx->block, 0, SM3_BLOCK_SIZE - 8);
	}

	count[0] = cpu_to_be32((ctx->nblocks) >> 23);
	count[1] = cpu_to_be32((ctx->nblocks << 9) + (ctx->num << 3));

	EasyXMen_sm3_compress(ctx->digest, ctx->block);
	for (i = 0; i < sizeof(ctx->digest) / sizeof(ctx->digest[0]); i++)
	{
		pdigest[i] = cpu_to_be32(ctx->digest[i]);
	}
}
/**
 * @description Computes the SM3 hash of a given message and stores the result in the provided output buffer.
 *        This function initializes the SM3 context, processes the input message, finalizes the hash computation,
 *        and securely clears the context to prevent leakage of sensitive information.
 * @param[in]  msg    : Pointer to the byte array containing the message to hash.
 * @param[in]  msglen : Length of the message byte array in bytes.
 * @param[out] dgst   : Pointer to an output buffer where the resulting 32-byte (256-bit) hash will be stored.
 * @return void
 */
void EasyXMen_sm3(const unsigned char *msg, unsigned int msglen, unsigned char *dgst)
{
	sm3_ctx_t ctx;

	EasyXMen_sm3_init(&ctx);
	EasyXMen_sm3_update(&ctx, msg, msglen);
	EasyXMen_sm3_final(&ctx, dgst);

	ILib_memset(&ctx, 0, sizeof(sm3_ctx_t));
}
